﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
using System.IO;

namespace asoptions
{
    public partial class PlaylistControl : UserControl
    {
        private const string REORDER = "reorder dammit";

        private int current;
        private bool plFromPlaylist;
        public bool playingFromPlaylist
        {
            get
            {
                return plFromPlaylist;
            }
            set
            {
                plFromPlaylist = value;
                if (!plFromPlaylist)
                {
                    playlistPlayButton.BackgroundImage = Properties.Resources.playImage;
                    if (playlistListView.Items.Count > 0)
                        playlistListView.Items[current].SubItems[3].Text = "";
                }
                else
                {
                    playlistPlayButton.BackgroundImage = Properties.Resources.pauseImage;
                }
            }
        }

        public delegate void OnLogMessage(LogMessageEventArgs e);
        public event OnLogMessage LogMessage;

        public delegate void OnPlaySong(string path, Characters ch);
        public event OnPlaySong PlaySong;

        private void sendToLog(string message, MessageType mt)
        {
            LogMessageEventArgs args = new LogMessageEventArgs();
            args.message = message;
            args.type = mt;
            LogMessage(args);
        }

        public PlaylistControl()
        {
            InitializeComponent();
            current = 0;
            playingFromPlaylist = false;
        }

        /// <summary>
        /// Add multiple songs to the playlist.
        /// </summary>
        /// <param name="path">Array of filenames (including paths)</param>
        /// <param name="fn">Array of filenames (excluding paths)</param>
        public void addToPlaylist(string[] path, string[] fn)
        {
            for (int k = 0; k < path.GetLength(0); k++)
            {
                addToPlaylist(path[k], fn[k]);
            }
        }

        /// <summary>
        /// Add a song to the playlist
        /// </summary>
        /// <param name="path">Full path to the song</param>
        /// <param name="fn">File name of the song (excluding path)</param>
        public void addToPlaylist(string path, string fn)
        {
            Song toAdd = new Song(path, fn);
            addToPlaylist(toAdd);
        }

        /// <summary>
        /// Add a song to the playlist
        /// </summary>
        /// <param name="s">Song to add</param>
        public void addToPlaylist(Song s)
        {
            ListViewItem item = new ListViewItem(s.fileName);
            item.SubItems.Add(s.artist + " - " + s.title);
            item.SubItems.Add(Enum.GetName(typeof(Characters), s.aschar));
            item.SubItems.Add("");
            item.Tag = s.fullPath;
            playlistListView.Items.Add(item);
            playlistPlayButton.Enabled = true;
            playlistSaveButton.Enabled = true;
            sendToLog("[Added \"" + s.ToString() + "\" to the playlist]", MessageType.Info);
        }

        /// <summary>
        /// Enable/disable components. These components should be enabled when something is selected, disabled otherwise.
        /// </summary>
        /// <param name="select">Enable or disable the components</param>
        private void itemSelect(bool select)
        {
            charSelectComboBox.Enabled = select;
            //playlistPlayButton.Enabled = select;
            playlistRemoveButton.Enabled = select;
        }

        /// <summary>
        /// Removes all selected items from the playlist.
        /// </summary>
        /// <returns>List of the items that were removed</returns>
        private List<ListViewItem> removeSelected()
        {
            List<ListViewItem> delitems = new List<ListViewItem>();
            playlistListView.BeginUpdate();
            while (playlistListView.SelectedItems.Count > 0)
            {
                delitems.Add(playlistListView.SelectedItems[0]);
                sendToLog("[Removing \"" + playlistListView.SelectedItems[0].Text +
                          " (" + playlistListView.SelectedItems[0].SubItems[1].Text +
                          ")\" from the playlist]", MessageType.Info);
                playlistListView.Items.RemoveAt(playlistListView.SelectedItems[0].Index);
            }
            playlistListView.EndUpdate();
            if (playlistListView.Items.Count == 0)
            {
                playlistPlayButton.Enabled = false;
                playlistSaveButton.Enabled = false;
            }
            return delitems;
        }

        /// <summary>
        /// Updates the character selection combo box depending on what items in the playlist are selected.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void playlistListView_SelectedIndexChanged(object sender, EventArgs e)
        {
            int selectedIndex = 0;
            if (playlistListView.SelectedItems.Count == 0)
            {
                charSelectComboBox.SelectedIndex = 0;
                itemSelect(false);
            }
            else
            {
                selectedIndex = (int)Enum.Parse(typeof(Characters), playlistListView.SelectedItems[0].SubItems[2].Text) + 1;
                itemSelect(true);
            }
            // see if all selected items have the same char, if chars differ, then the text box should be blank
            foreach (ListViewItem item in playlistListView.SelectedItems)
            {
                int index = (int)Enum.Parse(typeof(Characters), item.SubItems[2].Text) + 1;
                if (index != selectedIndex)
                {
                    charSelectComboBox.SelectedIndex = 0;
                    return;
                }
            }
            charSelectComboBox.SelectedIndex = selectedIndex;
        }

        /// <summary>
        /// Updates selected items in the playlist with the selected character in the combobox
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void charSelectComboBox_SelectedIndexChanged(object sender, EventArgs e)
        {
            if (charSelectComboBox.SelectedIndex == 0)
                return;
            foreach (ListViewItem item in playlistListView.SelectedItems)
            {
                item.SubItems[2].Text = charSelectComboBox.SelectedItem.ToString();
            }
        }

        private void playlistRemoveButton_Click(object sender, EventArgs e)
        {
            removeSelected();
        }

        #region drag drop

        // based on code from:
        // http://support.microsoft.com/kb/822483/en-us
        // http://www.codeproject.com/KB/list/dragdroplistviewreorder1.aspx

        private void playlistListView_ItemDrag(object sender, ItemDragEventArgs e)
        {
            playlistListView.DoDragDrop(REORDER, DragDropEffects.Move);
        }

        private void playlistListView_DragEnter(object sender, DragEventArgs e)
        {
            for (int i = 0; i < e.Data.GetFormats().Length; i++)
            {
                string text = (string)e.Data.GetData(REORDER.GetType());
                if (text.Equals(REORDER))
                {
                    e.Effect = DragDropEffects.Move;
                }
                else
                    sendToLog(text, MessageType.Warning);
            }
        }

        private void playlistListView_DragOver(object sender, DragEventArgs e)
        {
            Point cp = playlistListView.PointToClient(new Point(e.X, e.Y));
            ListViewItem hoverItem = playlistListView.GetItemAt(cp.X, cp.Y);
            if (hoverItem == null)
                return;
            foreach (ListViewItem item in playlistListView.SelectedItems)
            {
                if (item.Index == hoverItem.Index)
                {
                    hoverItem.EnsureVisible();
                    return;
                }
            }
            hoverItem.EnsureVisible();
        }

        private void playlistListView_DragDrop(object sender, DragEventArgs e)
        {
            if (playlistListView.SelectedItems.Count == 0)
                return;
            // get the location of the mouse in the listview
            Point cp = playlistListView.PointToClient(new Point(e.X, e.Y));
            ListViewItem dragToItem = playlistListView.GetItemAt(cp.X, cp.Y);
            if (dragToItem == null)
                return;
            int dragIndex = dragToItem.Index;
            ListViewItem[] sel = new ListViewItem[playlistListView.SelectedItems.Count];
            for (int i = 0; i < playlistListView.SelectedItems.Count; i++)
            {
                sel[i] = playlistListView.SelectedItems[i];
            }
            for (int i = 0; i < sel.GetLength(0); i++)
            {
                ListViewItem dragItem = sel[i];
                int itemIndex = dragIndex;
                if (itemIndex == dragItem.Index)
                    return;
                if (dragItem.Index < itemIndex)
                    itemIndex++;
                else
                    itemIndex = dragIndex + i;
                ListViewItem insertItem = (ListViewItem)dragItem.Clone();
                playlistListView.Items.Insert(itemIndex, insertItem);
                playlistListView.Items.Remove(dragItem);
            }
        }

        #endregion

        private delegate void playCur();
        /// <summary>
        /// Play the current song.
        /// </summary>
        private void playCurrent()
        {
            if (playlistListView.InvokeRequired)
            {
                playlistListView.Invoke(new playCur(playCurrent));
            }
            else
            {
                playlistListView.Items[current].SubItems[3].Text = ">";
                Characters ch = (Characters)Enum.Parse(typeof(Characters), playlistListView.Items[current].SubItems[2].Text);
                PlaySong((string)playlistListView.Items[current].Tag, ch);
                playingFromPlaylist = true;
                ASInterface.state = ASInterface.state | CurrentState.InSong;
            }
        }

        /// <summary>
        /// Begin playing the current song and enable playlist playback.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void playlistPlayButton_Click(object sender, EventArgs e)
        {
            if (!playingFromPlaylist)
            {
                sendToLog("[Playlist playback enabled]", MessageType.Info);
                playlistPlayButton.BackgroundImage = Properties.Resources.pauseImage;
                playCurrent();
            }
            else
            {
                sendToLog("[Playlist playback disabled]", MessageType.Info);
                playlistPlayButton.BackgroundImage = Properties.Resources.playImage;
                playingFromPlaylist = false;
            }
        }

        /// <summary>
        /// Play the next song in the playlist. Disables playlist playback if it reaches the last song.
        /// </summary>
        public void nextSong()
        {
            playlistListView.Items[current].SubItems[3].Text = "";
            current++;
            if (current >= playlistListView.Items.Count)
            {
                current = 0;
                playingFromPlaylist = false;
            }
            else
            {
                playlistBackgroundWorker.RunWorkerAsync();
            }
        }

        /// <summary>
        /// Play a song in the playlist.
        /// </summary>
        /// <param name="index">Index in the playlist of the song to play</param>
        public void nextSong(int index)
        {
            playlistListView.Items[current].SubItems[3].Text = "";
            current = index;
            if (playingFromPlaylist)
                playCurrent();
            else
                playlistPlayButton.PerformClick();
        }

        /// <summary>
        /// Wait the required amount of time, play the current song in the playlist.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void playlistBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            sendToLog("[Starting next song in " + timeBtSongsNumericUpDown.Value + " seconds]", MessageType.Warning);
            Thread.Sleep(new TimeSpan(0, 0, (int)timeBtSongsNumericUpDown.Value));
            playCurrent();
        }

        private void playlistListView_MouseDoubleClick(object sender, MouseEventArgs e)
        {
            var item = playlistListView.GetItemAt(e.X, e.Y);
            if (item == null)
                return;
            nextSong(item.Index);
        }

        private void playlistAddSongButton_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.InitialDirectory = Environment.GetFolderPath(Environment.SpecialFolder.MyMusic);
            ofd.Filter = "Audio files (*.mp3;*.m4a;*.ogg;*.wma)|*.mp3;*.m4a;*.ogg;*.wma";
            ofd.FilterIndex = 1;
            ofd.Multiselect = true;
            var result = ofd.ShowDialog();
            if (result == DialogResult.OK)
            {
                addToPlaylist(ofd.FileNames, ofd.SafeFileNames);
            }
        }

        #region playlist save/open

        private void playlistOpenButton_Click(object sender, EventArgs e)
        {
            OpenFileDialog ofd = new OpenFileDialog();
            ofd.InitialDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".asoptions");
            ofd.Filter = "M3U Playlist (*.m3u)|*.m3u";
            ofd.FilterIndex = 1;
            ofd.Multiselect = false;
            var result = ofd.ShowDialog();
            if (result == DialogResult.OK)
            {
                playlistListView.Items.Clear();
                openPlaylistM3U(ofd.FileName);
            }
        }

        // TODO: allow saving in a playlist format that will save character for each song
        //       (will require making my own format (xml or something else?))
        private void playlistSaveButton_Click(object sender, EventArgs e)
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.InitialDirectory = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), ".asoptions");
            sfd.Filter = "M3U Playlist (*.m3u)|*.m3u";
            sfd.FilterIndex = 1;
            sfd.FileName = "playlist1.m3u";
            var result = sfd.ShowDialog();
            if (result == DialogResult.OK)
            {
                savePlaylistM3U(sfd.FileName);
            }
        }

        /// <summary>
        /// Save the current playlist as a M3U file
        /// </summary>
        /// <param name="filename">Full path of the playlist to save</param>
        private void savePlaylistM3U(string filename)
        {
            StreamWriter outfile = new StreamWriter(filename);
            for (int k = 0; k < playlistListView.Items.Count; k++)
                outfile.WriteLine(playlistListView.Items[k].Tag);
            outfile.Close();
            sendToLog("[Saved playlist \"" + filename + "\"]", MessageType.Info);
        }

        /// <summary>
        /// Open a M3U playlist
        /// </summary>
        /// <param name="filename">Path of the playlist to open</param>
        private void openPlaylistM3U(string filename)
        {
            StreamReader infile = new StreamReader(filename);
            string line;
            int linenum = 1;
            while (!infile.EndOfStream)
            {
                line = infile.ReadLine();
                Path.GetExtension(line);
                if (File.Exists(line))
                {
                    string ext = Path.GetExtension(line);
                    if (ext == ".mp3" || ext == ".m4a" || ext == ".wma" || ext == ".ogg")
                        addToPlaylist(line, Path.GetFileName(line));
                    else
                        sendToLog("Line " + linenum + ": file type not supported; skipping. (\"" + line + "\")", MessageType.Warning);
                }
                else
                    sendToLog("Line " + linenum + ": file does not exist or is not supported; skipping. (\"" + line + "\")", MessageType.Warning);
                linenum++;
            }
            infile.Close();
            sendToLog("[Opened playlist \"" + filename + "\"]", MessageType.Info);
        }

        #endregion
    }
}
